/*!
 * \file TraderSpi.cpp
 * \project WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief CTP交易SPI回调接口实现
 * 
 * \details 实现了CTP合约加载器的交易SPI回调接口
 *          主要功能包括：
 *          - 连接CTP交易服务器并进行身份验证
 *          - 查询所有可交易的合约信息
 *          - 解析合约数据并分类为品种和合约
 *          - 将数据导出为JSON格式文件供交易系统使用
 *          - 支持期货、期权等多种产品类型
 *          - 支持自定义品种名称和交易时段映射
 */

#include <iostream>
#include <map>
#include <set>
#include <stdint.h>
#include <fstream>
#include <exception>

#include "../Share/StrUtil.hpp"
#include "../Includes/WTSTypes.h"

#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
namespace rj = rapidjson;

#include "TraderSpi.h"


USING_NS_WTP;

// 外部映射表，用于品种名称和交易时段的自定义映射
extern std::map<std::string, std::string>	MAP_NAME;		/*!< 品种名称映射表 */
extern std::map<std::string, std::string>	MAP_SESSION;	/*!< 交易时段映射表 */

#pragma warning(disable : 4996)

// USER_API对象
extern CThostFtdcTraderApi* pUserApi;

// 配置参数
extern std::string	FRONT_ADDR;		/*!< 前置地址 */
extern std::string	BROKER_ID;		/*!< 经纪公司代码 */
extern std::string	INVESTOR_ID;	/*!< 投资者代码 */
extern std::string	PASSWORD;		/*!< 用户密码 */
extern std::string	SAVEPATH;		/*!< 保存位置 */
extern std::string	APPID;			/*!< 应用标识 */
extern std::string	AUTHCODE;		/*!< 认证码 */
extern uint32_t		CLASSMASK;		/*!< 产品类型掩码 */

extern std::string COMM_FILE;		/*!< 输出的品种文件名 */
extern std::string CONT_FILE;		/*!< 输出的合约文件名 */

// 请求编号
extern int iRequestID;

// 会话参数
TThostFtdcFrontIDType	FRONT_ID;	/*!< 前置编号 */
TThostFtdcSessionIDType	SESSION_ID;	/*!< 会话编号 */
TThostFtdcOrderRefType	ORDER_REF;	/*!< 报单引用 */

/*!
 * \struct _Commodity
 * \brief 品种信息结构体
 * 
 * \details 存储从CTP查询到的品种基本信息，包括名称、交易所、
 *          价格精度、合约乘数、交易模式等关键参数
 */
typedef struct _Commodity
{
	std::string	m_strName;		/*!< 品种名称 */
	std::string	m_strExchg;		/*!< 交易所代码 */
	std::string	m_strProduct;	/*!< 产品代码 */
	std::string	m_strCurrency;	/*!< 币种 */
	std::string m_strSession;	/*!< 交易时段 */

	uint32_t	m_uVolScale;	/*!< 合约乘数 */
	double		m_fPriceTick;	/*!< 最小变动价位 */
	uint32_t	m_uPrecision;	/*!< 价格精度 */

	ContractCategory	m_ccCategory;	/*!< 合约分类 */
	CoverMode			m_coverMode;	/*!< 平仓模式 */
	PriceMode			m_priceMode;	/*!< 价格模式 */
	tagTradingMode		m_tradeMode;	/*!< 交易模式 */

} Commodity;
typedef std::map<std::string, Commodity> CommodityMap;
CommodityMap _commodities;	/*!< 品种信息映射表 */

/*!
 * \struct _Contract
 * \brief 合约信息结构体
 * 
 * \details 存储从CTP查询到的合约详细信息，包括合约代码、名称、
 *          最大下单量、期权相关信息等
 */
typedef struct _Contract
{
	std::string	m_strCode;		/*!< 合约代码 */
	std::string	m_strExchg;		/*!< 交易所代码 */
	std::string	m_strName;		/*!< 合约名称 */
	std::string	m_strProduct;	/*!< 产品代码 */

	uint32_t	m_maxMktQty;	/*!< 市价单最大下单量 */
	uint32_t	m_maxLmtQty;	/*!< 限价单最大下单量 */

	OptionType	m_optType;		/*!< 期权类型 */
	std::string m_strUnderlying;/*!< 标的合约 */
	double		m_strikePrice;	/*!< 执行价 */
	double		m_dUnderlyingScale;	/*!< 标的乘数 */
} Contract;
typedef std::map<std::string, Contract> ContractMap;
ContractMap _contracts;		/*!< 合约信息映射表 */

/*!
 * \brief 从合约代码中提取产品代码
 * 
 * \param instrument 合约代码，如"cu2012"
 * \return 产品代码，如"cu"
 * 
 * \details 通过解析合约代码的字母部分来获取产品代码
 */
std::string extractProductID(const char* instrument)
{
	std::string strRet;
	int nLen = 0;
	while ('A' <= instrument[nLen] && instrument[nLen] <= 'z')
	{
		strRet += instrument[nLen];
		nLen++;
	}

	return strRet;
}

/*!
 * \brief 从合约名称中提取产品名称
 * 
 * \param cname 合约名称，如"沪铜2012"
 * \return 产品名称，如"沪铜"
 * 
 * \details 通过去除合约名称末尾的数字部分来获取产品名称
 */
std::string extractProductName(const char* cname)
{
	std::string strRet;
	int idx = strlen(cname) - 1;
	while (isdigit(cname[idx]) && idx > 0)
	{
		idx--;
	}

	strRet.append(cname, idx + 1);
	return strRet;
}

std::set<std::string>	prod_set;	/*!< 产品代码集合，用于去重 */

/*!
 * \brief 转换无效的浮点数值
 * 
 * \param val 原始数值
 * \return 转换后的数值，无效值返回-1
 * 
 * \details CTP API中某些无效的浮点数用特殊值表示，需要转换为-1
 */
double convertInvalidDouble(double val)
{
	if (val == 5.5e-007)
		return -1;

	if (val == 0)
		return -1;

	return val;
}

/*!
 * \brief 前置连接成功回调
 * 
 * \details 当与CTP交易前置建立连接后，自动发起身份验证请求
 */
void CTraderSpi::OnFrontConnected()
{
	std::cerr << "--->>> " << "OnFrontConnected" << std::endl;
	///�û���¼����
	ReqAuth();
}

void CTraderSpi::ReqAuth()
{
	CThostFtdcReqAuthenticateField req;
	memset(&req, 0, sizeof(req));
	strcpy(req.BrokerID, BROKER_ID.c_str());
	strcpy(req.UserID, INVESTOR_ID.c_str());
	//strcpy(req.UserProductInfo, m_strProdInfo.c_str());
	strcpy(req.AuthCode, AUTHCODE.c_str());
	strcpy(req.AppID, APPID.c_str());
	int iResult = pUserApi->ReqAuthenticate(&req, ++iRequestID);
	std::cerr << "--->>> 发送终端认证请求: " << ((iResult == 0) ? "成功" : "失败") << std::endl;
}

void CTraderSpi::OnRspAuthenticate(CThostFtdcRspAuthenticateField *pRspAuthenticateField, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
	std::cerr << "--->>> " << "OnRspAuthenticate" << std::endl;
	if (bIsLast && !IsErrorRspInfo(pRspInfo))
	{
		ReqUserLogin();
	}
}

void CTraderSpi::ReqUserLogin()
{
	CThostFtdcReqUserLoginField req;
	memset(&req, 0, sizeof(req));
	strcpy(req.BrokerID, BROKER_ID.c_str());
	strcpy(req.UserID, INVESTOR_ID.c_str());
	strcpy(req.Password, PASSWORD.c_str());
	int iResult = pUserApi->ReqUserLogin(&req, ++iRequestID);
	std::cerr << "--->>> 发送用户登录请求: " << ((iResult == 0) ? "成功" : "失败") << std::endl;
}

void CTraderSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin,
	CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
	std::cerr << "--->>> " << "OnRspUserLogin" << std::endl;
	if (bIsLast && !IsErrorRspInfo(pRspInfo))
	{
		// 保存会话参数
		FRONT_ID = pRspUserLogin->FrontID;
		SESSION_ID = pRspUserLogin->SessionID;
		int iNextOrderRef = atoi(pRspUserLogin->MaxOrderRef);
		iNextOrderRef++;
		sprintf(ORDER_REF, "%d", iNextOrderRef);
		// 获取当前交易日
		m_lTradingDate = atoi(pUserApi->GetTradingDay());

		ReqQryInstrument();
	}
}

void CTraderSpi::ReqQryInstrument()
{
	CThostFtdcQryInstrumentField req;
	memset(&req, 0, sizeof(req));
	int iResult = pUserApi->ReqQryInstrument(&req, ++iRequestID);
	std::cerr << "--->>> 请求查询合约: " << ((iResult == 0) ? "成功" : "失败") << std::endl;
}

/*!
 * \brief 判断是否为期权产品
 * 
 * \param pClass 产品类型
 * \return true-期权产品，false-非期权产品
 */
inline bool isOption(TThostFtdcProductClassType pClass)
{
	if (pClass == THOST_FTDC_PC_Options || pClass == THOST_FTDC_PC_SpotOption || pClass == THOST_FTDC_PC_SpotOption)
		return true;

	return false;
}

/*!
 * \brief 判断是否为期货产品
 * 
 * \param pClass 产品类型
 * \return true-期货产品，false-非期货产品
 */
inline bool isFuture(TThostFtdcProductClassType pClass)
{
	return pClass == THOST_FTDC_PC_Futures;
}

/*!
 * \brief 将CTP产品类型转换为WonderTrader合约分类
 * 
 * \param cType CTP产品类型
 * \return WonderTrader合约分类枚举值
 */
inline ContractCategory wrapCategory(TThostFtdcProductClassType cType)
{
	switch (cType)
	{
	case THOST_FTDC_PC_Futures: return CC_Future;
	case THOST_FTDC_PC_Options: return CC_FutOption;
	case THOST_FTDC_PC_Combination: return CC_Combination;
	case THOST_FTDC_PC_Spot: return CC_Spot;
	case THOST_FTDC_PC_EFP: return CC_EFP;
	case THOST_FTDC_PC_SpotOption: return CC_SpotOption;
	default:
		throw std::runtime_error("non implemented category");
	}
}

/*!
 * \brief 查询合约响应回调
 * 
 * \param pInstrument 合约信息结构体
 * \param pRspInfo 响应信息
 * \param nRequestID 请求ID
 * \param bIsLast 是否最后一条
 * 
 * \details 处理从CTP服务器返回的合约信息，包括：
 *          - 根据配置的产品类型掩码过滤合约
 *          - 解析合约名称和品种名称
 *          - 构建合约和品种信息结构
 *          - 处理期货和期权的不同逻辑
 *          - 设置交易所特定的交易规则
 *          - 当所有合约查询完成时导出JSON文件
 */
void CTraderSpi::OnRspQryInstrument(CThostFtdcInstrumentField *pInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
	if (!IsErrorRspInfo(pRspInfo))
	{
		if (pInstrument)
		{
			bool bOption = isOption(pInstrument->ProductClass);	// 是否为期权
			bool bFuture = isFuture(pInstrument->ProductClass);	// 是否为期货

			// 根据配置的产品类型掩码判断是否需要处理该合约
			bool isGranted = false;
			if (bOption && (CLASSMASK & 2) != 0)		// 期权掩码位
				isGranted = true;
			else if (bFuture && (CLASSMASK & 1) != 0)	// 期货掩码位
				isGranted = true;

			if (isGranted)
			{
				std::cerr << "--->>> OnRspQryInstrument: " << pInstrument->ExchangeID << "." << pInstrument->InstrumentID << std::endl;
				
				// 获取品种名称，优先使用映射表中的自定义名称
				std::string pname = MAP_NAME[pInstrument->ProductID];
				if (pname.empty())
				{
					// 尝试使用"交易所.产品代码"的格式查找映射
					std::stringstream ss;
					ss << pInstrument->ExchangeID << "." << pInstrument->ProductID;
					pname = MAP_NAME[ss.str()];
				}

				// 构建合约名称
				std::string cname = "";
				if (pname.empty())
				{
					// 没有自定义映射，使用CTP返回的原始名称
					cname = pInstrument->InstrumentName;
					pname = bFuture ? extractProductName(pInstrument->InstrumentName) : pInstrument->InstrumentName;
				}
				else
				{
					// 有自定义映射，根据产品类型构建合约名称
					if(bFuture)
					{
						// 期货：品种名称 + 合约月份
						std::string month = pInstrument->InstrumentID;
						month = month.substr(strlen(pInstrument->ProductID));
						cname = pname + month;
					}
					else if (bOption)
					{
						// 期权：品种名称 + 期权标识
						std::string underlyPID = extractProductID(pInstrument->UnderlyingInstrID);
						std::string month = pInstrument->InstrumentID;
						month = month.substr(underlyPID.size());
						cname = pname + month;
					}
					else
					{
						cname = pInstrument->InstrumentName;
					}
				}

				// 构建合约信息
				Contract contract;
				contract.m_strCode = pInstrument->InstrumentID;		// 合约代码
				contract.m_strExchg = pInstrument->ExchangeID;		// 交易所
				contract.m_strName = StrUtil::trim(cname.c_str());	// 合约名称
				contract.m_strProduct = pInstrument->ProductID;		// 产品代码
				contract.m_maxMktQty = pInstrument->MaxMarketOrderVolume;	// 市价单最大下单量
				contract.m_maxLmtQty = pInstrument->MaxLimitOrderVolume;	// 限价单最大下单量

				// 期权相关信息
				contract.m_optType = bOption ? (OptionType)pInstrument->OptionsType : OT_None;
				contract.m_strUnderlying = pInstrument->UnderlyingInstrID;	// 标的合约
				contract.m_strikePrice = pInstrument->StrikePrice;			// 执行价
				contract.m_dUnderlyingScale = pInstrument->UnderlyingMultiple;	// 标的乘数

				// 检查品种信息是否已存在
				std::string key = StrUtil::printf("%s.%s", pInstrument->ExchangeID, pInstrument->ProductID);
				auto it = _commodities.find(key);
				if (it == _commodities.end())
				{
					// 构建品种信息
					Commodity commInfo;
					commInfo.m_strProduct = pInstrument->ProductID;
					commInfo.m_strName = StrUtil::trim(pname.c_str());
					commInfo.m_strExchg = pInstrument->ExchangeID;
					commInfo.m_strCurrency = "CNY";	// 默认人民币

					commInfo.m_strSession = MAP_SESSION[key];	// 交易时段映射
					commInfo.m_ccCategory = wrapCategory(pInstrument->ProductClass);

					commInfo.m_uVolScale = (pInstrument->VolumeMultiple == 0 ? 1 : pInstrument->VolumeMultiple);
					commInfo.m_fPriceTick = pInstrument->PriceTick;

					// 设置平仓模式（上期所和能源中心需要区分今仓和昨仓）
					CoverMode cm = CM_OpenCover;
					if (bFuture)
					{
						if (strcmp(pInstrument->ExchangeID, "SHFE") == 0 || strcmp(pInstrument->ExchangeID, "INE") == 0)
							cm = CM_CoverToday;
						// 上期所的今仓是平今，昨仓的就是开平
					}
					commInfo.m_coverMode = cm;

					// 设置价格模式（上期所和能源中心只支持限价单）
					PriceMode pm = PM_Both;
					if (bFuture)
					{
						if (strcmp(pInstrument->ExchangeID, "SHFE") == 0 || strcmp(pInstrument->ExchangeID, "INE") == 0)
							pm = PM_Limit;
					}
					commInfo.m_priceMode = pm;
					commInfo.m_tradeMode = TM_Both;

					// 根据最小变动价位设置价格精度
					if (pInstrument->PriceTick < 0.001)
						commInfo.m_uPrecision = 4;
					else if (pInstrument->PriceTick < 0.01)
						commInfo.m_uPrecision = 3;
					else if (pInstrument->PriceTick < 0.1)
						commInfo.m_uPrecision = 2;
					else if (pInstrument->PriceTick < 1)
						commInfo.m_uPrecision = 1;
					else
						commInfo.m_uPrecision = 0;

					_commodities[key] = commInfo;
				}

				// 保存合约信息
				key = StrUtil::printf("%s.%s", pInstrument->ExchangeID, pInstrument->InstrumentID);
				_contracts[key] = contract;
			}
		}
	}

	// 如果是最后一条响应，导出JSON文件并退出程序
	if (bIsLast)
	{
		DumpToJson();
		exit(0);
	}
}

/*!
 * \brief 将合约和品种信息导出为JSON文件
 * 
 * \details 将内存中的合约和品种信息分别导出为两个JSON文件：
 *          - commodities.json：品种信息文件
 *          - contracts.json：合约信息文件
 *          文件格式按照WonderTrader的标准格式组织
 */
void CTraderSpi::DumpToJson()
{
	// 导出文件，一个contracts.json，一个commodities.json
	rj::Document jComms(rj::kObjectType);
	{
		rj::Document::AllocatorType &allocator = jComms.GetAllocator();
		for (auto it = _commodities.begin(); it != _commodities.end(); it++)
		{
			const Commodity& commInfo = it->second;
			if (!jComms.HasMember(commInfo.m_strExchg.c_str()))
			{
				// 为每个交易所创建对象
				jComms.AddMember(rj::Value(commInfo.m_strExchg.c_str(), allocator), rj::Value(rj::kObjectType), allocator);
			}

			// 构建品种信息JSON对象
			rj::Value jComm(rj::kObjectType);
			jComm.AddMember("covermode", (uint32_t)commInfo.m_coverMode, allocator);
			jComm.AddMember("pricemode", (uint32_t)commInfo.m_priceMode, allocator);
			jComm.AddMember("category", (uint32_t)commInfo.m_ccCategory, allocator);
			jComm.AddMember("trademode", (uint32_t)commInfo.m_tradeMode, allocator);
			jComm.AddMember("precision", commInfo.m_uPrecision, allocator);
			jComm.AddMember("pricetick", commInfo.m_fPriceTick, allocator);
			jComm.AddMember("volscale", commInfo.m_uVolScale, allocator);

			jComm.AddMember("name", rj::Value(commInfo.m_strName.c_str(), allocator), allocator);
			jComm.AddMember("exchg", rj::Value(commInfo.m_strExchg.c_str(), allocator), allocator);
			jComm.AddMember("session", rj::Value(commInfo.m_strSession.c_str(), allocator), allocator);
			jComm.AddMember("holiday", rj::Value("CHINA", allocator), allocator);

			// 将品种信息添加到对应交易所下
			jComms[commInfo.m_strExchg.c_str()].AddMember(rj::Value(commInfo.m_strProduct.c_str(), allocator), jComm, allocator);
		}
	}

	// 构建合约信息JSON
	rj::Document jContracts(rj::kObjectType);
	{
		rj::Document::AllocatorType &allocator = jContracts.GetAllocator();
		for (auto it = _contracts.begin(); it != _contracts.end(); it++)
		{
			const Contract& cInfo = it->second;
			if (!jContracts.HasMember(cInfo.m_strExchg.c_str()))
			{
				// 为每个交易所创建对象
				jContracts.AddMember(rj::Value(cInfo.m_strExchg.c_str(), allocator), rj::Value(rj::kObjectType), allocator);
			}

			// 构建合约信息JSON对象
			rj::Value jcInfo(rj::kObjectType);
			jcInfo.AddMember("name", rj::Value(cInfo.m_strName.c_str(), allocator), allocator);
			jcInfo.AddMember("code", rj::Value(cInfo.m_strCode.c_str(), allocator), allocator);
			jcInfo.AddMember("exchg", rj::Value(cInfo.m_strExchg.c_str(), allocator), allocator);
			jcInfo.AddMember("product", rj::Value(cInfo.m_strProduct.c_str(), allocator), allocator);
			jcInfo.AddMember("maxlmt", cInfo.m_maxLmtQty, allocator);
			jcInfo.AddMember("maxmkt", cInfo.m_maxMktQty, allocator);

			// 期权相关信息
			if (cInfo.m_optType != OT_None)
			{
				rj::Value optInfo(rj::kObjectType);
				optInfo.AddMember("underlying", rj::Value(cInfo.m_strUnderlying.c_str(), allocator), allocator);
				optInfo.AddMember("strikeprice", cInfo.m_strikePrice, allocator);
				optInfo.AddMember("underlyingscale", cInfo.m_dUnderlyingScale, allocator);
				optInfo.AddMember("optiontype", (uint32_t)cInfo.m_optType, allocator);

				jcInfo.AddMember("option", optInfo, allocator);
			}

			// 将合约信息添加到对应交易所下
			jContracts[cInfo.m_strExchg.c_str()].AddMember(rj::Value(cInfo.m_strCode.c_str(), allocator), jcInfo, allocator);
		}
	}

	// 写入品种文件
	{
		rj::StringBuffer sb;
		rj::PrettyWriter<rj::StringBuffer> writer(sb);
		jComms.Accept(writer);

		std::string filename = SAVEPATH + COMM_FILE;
		std::ofstream ofs;
		ofs.open(filename.c_str());
		ofs << sb.GetString();
		ofs.close();
	}
	
	// 写入合约文件
	{
		rj::StringBuffer sb;
		rj::PrettyWriter<rj::StringBuffer> writer(sb);
		jContracts.Accept(writer);

		std::string filename = SAVEPATH + CONT_FILE;
		std::ofstream ofs;
		ofs.open(filename.c_str());
		ofs << sb.GetString();
		ofs.close();
	}
}

/*!
 * \brief 前置连接断开回调
 * 
 * \param nReason 断开原因代码
 * 
 * \details 当与CTP交易前置的连接断开时被调用
 *          API会自动重连，客户端通常不需要特殊处理
 */
void CTraderSpi::OnFrontDisconnected(int nReason)
{
	std::cerr << "--->>> " << "OnFrontDisconnected" << std::endl;
	std::cerr << "--->>> Reason = " << nReason << std::endl;
}

/*!
 * \brief 错误响应回调
 * 
 * \param pRspInfo 响应信息，包含错误代码和错误消息
 * \param nRequestID 请求ID
 * \param bIsLast 是否最后一条
 * 
 * \details 当API调用发生错误时被调用，输出错误信息
 */
void CTraderSpi::OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
	std::cerr << "--->>> " << "OnRspError" << std::endl;
	IsErrorRspInfo(pRspInfo);
}

/*!
 * \brief 检查响应信息是否包含错误
 * 
 * \param pRspInfo 响应信息结构体
 * \return true-包含错误，false-成功响应
 * 
 * \details 检查CTP API响应中的错误代码，如果有错误则输出错误信息
 */
bool CTraderSpi::IsErrorRspInfo(CThostFtdcRspInfoField *pRspInfo)
{
	// 如果ErrorID != 0, 说明收到了错误的响应
	bool bResult = ((pRspInfo) && (pRspInfo->ErrorID != 0));
	if (bResult)
		std::cerr << "--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg=" << pRspInfo->ErrorMsg << std::endl;
	return bResult;
}